﻿package fr.babos.core.data.remote 
{
	/**
	 * Les imports
	 */
	import flash.events.AsyncErrorEvent;
	import flash.events.ErrorEvent;
	import flash.events.IOErrorEvent;
	import flash.events.NetStatusEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.NetConnection;
	import flash.net.Responder;
	import fr.babos.core.data.remote.ADataRemote;
	import fr.babos.core.data.remote.IDataRemote;
	import fr.babos.core.events.DynamicErrorEvent;
	import fr.babos.core.events.RemoteEvent;
	import fr.babos.core.roles.IDisposable;
	
	/**
	 * La remote AMF
	 * @author ZoulouX
	 */
	public class AMFRemote extends ADataRemote implements IDataRemote, IDisposable
	{
		/**
		 * La NetConnection
		 */
		protected var _netConnection				:NetConnection;
		
		/**
		 * Récupérer la netConnection
		 */
		public function get netConnection ():NetConnection { return _netConnection; }
		
		
		/**
		 * Le constructeur de la remote.
		 * @param	pGatewayURI : L'URI de la passerelle AMF. Serveur FMS ou passerelle AMF classique.
		 * @param	pNetConnection : Une netConnection spécifique peut être passée en paramètre
		 */
		public function AMFRemote (pGatewayURI:String = "", pNetConnection:NetConnection = null)
		{
			// Vérifier si on a une netConnection
			if (pNetConnection != null)
				_netConnection = pNetConnection;
			else
				_netConnection = new NetConnection();
			
			// Ecouter les events
			registerEvents();
			
			// Se connecter si on a une URL
			if (pGatewayURI != "")
				_netConnection.connect(pGatewayURI);
		}
		
		/**
		 * Ecouter les évènements
		 */
		protected function registerEvents ():void
		{
			_netConnection.addEventListener(NetStatusEvent.NET_STATUS, 				netStatusHandler);
			_netConnection.addEventListener(IOErrorEvent.IO_ERROR, 					asyncErrorHandler);
			_netConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, 		asyncErrorHandler);
			_netConnection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, 			asyncErrorHandler);
		}
		
		/**
		 * Ne plus écouter les évènements
		 */
		protected function unregisterEvents ():void
		{
			_netConnection.removeEventListener(NetStatusEvent.NET_STATUS, 			netStatusHandler);
			_netConnection.removeEventListener(IOErrorEvent.IO_ERROR, 				asyncErrorHandler);
			_netConnection.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, 	asyncErrorHandler);
			_netConnection.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, 		asyncErrorHandler);
		}
		
		/**
		 * Appeler un service AMF.
		 * @param	pCommand : Le nom de la commande AMF, par exemple "MainController.mainAction"
		 * @param	pOptions : Les options de cet appel. Cet objet peut contenir une fonction onSuccess, onError, et un flag immediateCall. Si ce dernier est à false, la méthode executée du RemotingCall devra être appelée manuellement.
		 * @param	... rest : Les paramètres à passer à la commande AMF
		 * @return
		 */
		override public function call (pCommand:String, pOptions:Object = null, ... rest):RemotingCall
		{
			// Créer un nouvel id de call
			_callId ++;
			
			// Créer l'objet remotingCall
			var caller:RemotingCall = new RemotingCall(_callId, pCommand, _netConnection.call, pOptions == null ? {} : pOptions);
			
			// Créer le responder, avec le délégations locales
			var responder:Responder = new Responder(function (pResponse:*):void {
				remoteSuccessHandler(pResponse, caller);
			}, function (pResponse:*):void {
				remoteErrorHandler(pResponse, caller);
			});
			
			// Spécifier les paramètres au caller
			// Le faire dans cet ordre permet de garder le scope du call
			caller.routineArguments = [pCommand, responder].concat(rest);
			
			// L'ajouter dans la pile
			_calls.push(caller);
			
			// Effectuer cet appel sauf si c'est spécifier dans les options
			if (pOptions == null || pOptions["immediateCall"] == null || pOptions["immediateCall"] == true)
				caller.execute();
			
			// Retourner le caller
			return caller;
		}
		
		/**
		 * Un appel à été réussi
		 * @param	pResponse : La réponse
		 * @param	pCaller : Le caller réussi
		 */
		protected function remoteSuccessHandler (pResponse:*, pCaller:RemotingCall):void
		{
			// L'objet de réponse
			var remoteResponse:RemotingResponse;
			
			// Tenter
			try
			{
				// De caster la réponse en RemotingResponse
				remoteResponse = new RemotingResponse(pResponse);	
			}
			catch (error:Error)
			{
				trace("REMOTE FORMAT ERROR");
				
				// Créer la response
				remoteResponse = new RemotingResponse();
				
				// Si c'est stricte
				if (_parser.strict)
				{
					// C'est une erreur
					remoteResponse.success = false;
					remoteResponse.message = "Invalid RemotingResponse received";
				}
				else
				{
					// On a qu'a dire que c'est ok
					remoteResponse.success = true;
					
					// On associe les données
					remoteResponse.response = pResponse;
				}
			}
			
			// Vérifier si on doit parser les données
			if (remoteResponse.response != null && _autoParse)
			{
				// Parser
				try
				{
					remoteResponse.response = _parser.parse(remoteResponse.response);
				}
				catch (e:Error)
				{
					trace("REMOTE PARSE ERROR");
				}
			}
			
			// Vérifier l'état de la remote
			if (remoteResponse.success)
			{
				// Déclancher une réussite
				dispatchEvent(new RemoteEvent(RemoteEvent.RESPONSE, pCaller));
				
				// Appeler la méthode des options
				if (pCaller.options['onSuccess'] != null)
					pCaller.options['onSuccess'](remoteResponse.response, pCaller);
			}
			else
			{
				// Déclancher une erreur dynamique
				dispatchEvent(new RemoteEvent(RemoteEvent.RESPONSE_ERROR, pCaller));
				
				// Appeler la méthode des options
				if (pCaller.options['onResponseError'] != null)
					pCaller.options['onResponseError'](remoteResponse.response, pCaller);
			}
		}
		
		/**
		 * Un appel à échoué
		 * @param	pResponse : La réponse
		 * @param	pCaller : Le caller
		 */
		protected function remoteErrorHandler (pResponse:*, pCaller:RemotingCall):void
		{
			
		}
		
		/**
		 * Erreur asynchrone sur la netConnection
		 * @param	event
		 */
		protected function asyncErrorHandler (event:ErrorEvent):void
		{
			// Vérifier le type d'erreur
			if (event is AsyncErrorEvent)
				// Dispatcher une erreur Async
				dispatchError("", 0);
			else if (event is IOErrorEvent)
				// Dispatcher une erreur IO
				dispatchError("", 0);
			else if (event is SecurityErrorEvent)
				// Dispatcher une erreur Security
				dispatchError("", 0);
		}
		
		/**
		 * Changement de status de la passerelle
		 * @param	event
		 */
		protected function netStatusHandler (event:NetStatusEvent):void 
		{
			// Vérifier le status
			if (event.info['code'] == "")
				// Dispatcher une erreur NetStatus
				dispatchError("", 0);
			else if (event.info['code'] == "")
				// Dispatcher une erreur NetStatus
				dispatchError("", 0);
			else if (event.info['code'] == "")
				// Dispatcher une erreur NetStatus
				dispatchError("", 0);
		}
		
		/**
		 * Dispatcher une erreur
		 */
		protected function dispatchError (pMessage:String, pCode:uint):void
		{
			// Créer l'event
			var event:RemoteEvent = new RemoteEvent(RemoteEvent.REMOTE_ERROR);
			
			// Spécifier le message et le code d'erreur
			event.message = pMessage;
			event.code = pCode;
			
			// Dispatcher l'event
			dispatchEvent(event);
		}
		
		/**
		 * Effacer
		 */
		override public function dispose ():void
		{
			// Ne plus écouter les events
			unregisterEvents();
			
			// Vider la pile de calls
			_calls = null;
		}
	}
}